/**
 * 
 */
package com.tagit.speech;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;

import javax.naming.NamingException;

import org.jvoicexml.ConnectionInformation;
import org.jvoicexml.Session;
import org.jvoicexml.client.text.TextListener;
import org.jvoicexml.client.text.TextServer;
import org.jvoicexml.event.ErrorEvent;
import org.jvoicexml.xml.ssml.SsmlDocument;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.api.IConnection;
import org.red5.server.api.service.IServiceCapableConnection;
import org.slf4j.Logger;

import com.tagit.red5.Application;
import com.tagit.sphinx4.SphinxRecognizer;

/**
 * A session representing the user connection to the server as well as the
 * associated JVoiceXML Session, TextServer and Speech Recognizer. The session
 * also implements the {@link TextListener} interface
 * 
 * @author Mohamed Ali
 */
public class SpeechSession implements TextListener {
	final private Logger log = Red5LoggerFactory.getLogger(this.getClass(),
			"flashMic");

	/** ClientID from red5 */
	private String id;

	/** The Session's Text Server */
	private TextServer textServer;

	/** The VoiceXML session created by JVoiceXML */
	private Session session;

	/** The {@link SpeechRecognizer} Associated with the session */
	private SpeechRecognizer recgonizer;

	/** The connection associated with the session */
	private IConnection conn;
	private IServiceCapableConnection sc;

	/** The initiating Application */
	private Application app;

	/** Lower end port for TextServer */
	public final int MIN_PORT = 4000;
	/** Higher end port for TextServer */
	public final int MAX_PORT = 5000;

	/** Whether the Recognizer is ready */
	private boolean recognizerReady = false;

	/** The Session language */
	private String lang = "";

	public SpeechSession(String id, IConnection conn, Application app) {
		super();
		this.id = id;
		this.conn = conn;
		this.sc = (IServiceCapableConnection) conn;
		this.app = app;
		sendClientID();
		log.info("Session created for client " + id);
	}

	/** Sends the red5 generated clientID to the flash client */
	private void sendClientID() {
		if (sc != null) {
			sc.invoke("setClientID", new Object[] { id });
		}
	}

	/** Sends a list of available VXML applications for the selected language */
	private void sendApps() {
		String baseURL = "";
		String basePath = "";
		try {
			baseURL = (String) app.envCtx.lookup("appsBaseURL");
			basePath = (String) app.envCtx.lookup("appsBasePath");
		} catch (NamingException e) {
			log.error("Error Reading Environment Variable", e);
		}

		if (sc != null) {
			File[] files = null;
			try {
				FileFilter filter = new FileFilter() {

					@Override
					public boolean accept(File pathname) {
						if (pathname.isFile()) {
							String filename = pathname.getName();
							int dot = filename.lastIndexOf(".");
							String ext = filename.substring(dot);
							if (ext.equals(".vxml"))
								return true;
							return false;
						}
						return false;
					}
				};
				files = app.getContext().getResource(basePath + lang).getFile()
						.listFiles(filter);
			} catch (IOException e) {
				log.error("Couldn't list files", e);
			}

			ArrayList<String> apps = new ArrayList<String>();
			for (File file : files) {
				apps.add(baseURL + lang + "/" + file.getName());
			}
			sc.invoke("setApps", new Object[] { apps.toArray() });
		}
	}

	private int getNextPort() {
		for (int i = MIN_PORT; i < MAX_PORT; i++) {
			ServerSocket ss = null;

			try {
				ss = new ServerSocket(i);
				ss.setReuseAddress(true);
				return i;
			} catch (IOException e) {

			} finally {
				if (ss != null) {
					try {
						ss.close();
					} catch (IOException e) {
						/* should not be thrown */
					}
				}
			}
		}
		return -1;
	}

	public void initSettings(String lang, int sampleRate) {
		this.lang = lang;

		synchronized (app.lock) {
			int port = getNextPort();
			if (port > 0) {
				log.debug("Initializing Text Server on port " + port);
				textServer = new TextServer(port);
			} else {
				// TODO: handle faulty port assignment
			}
		}

		recgonizer = new SphinxRecognizer(this, app.getContext(), lang,
				sampleRate);

		// Initialise the TextServer
		textServer.addTextListener(this);
		textServer.start();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jvoicexml.client.text.TextListener#started()
	 */
	@Override
	public void started() {
		log.info("Text Server started");
		ConnectionInformation info = null;
		try {
			info = textServer.getConnectionInformation();
		} catch (UnknownHostException e) {
			log.error("Couldn't get connection information", e);
		}

		try {
			session = app.jvxml.createSession(info);
			log.info("Created JVXML session");
			log.debug(info.toString());
		} catch (ErrorEvent e) {
			log.error("Couldn't create session", e);
		}

		while (!recognizerReady) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				log.error("Thread interrupted", e);
			}
		}

		sendApps();
	}

	public void readData(double[] data, int collectTime) {
		if (recgonizer.isRecognizing())
			recgonizer.readData(data, collectTime);
	}

	public void callURL(String path) {
		URI uri = null;
		try {
			uri = new URI(path);
		} catch (URISyntaxException e1) {
			log.error("Malformed URI " + path, e1);
		}
		try {
			session.call(uri);
			sessionConnected();
		} catch (ErrorEvent e) {
			log.debug("Error calling url " + path, e);
		}
	}

	public void sessionConnected() {
		if (sc != null) {
			sc.invoke("sessionConnected");
		}
	}

	public void hangUp() {
		session.hangup();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.jvoicexml.client.text.TextListener#connected(java.net.InetSocketAddress
	 * )
	 */
	@Override
	public void connected(InetSocketAddress arg0) {
		// TODO Auto-generated method stub

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jvoicexml.client.text.TextListener#disconnected()
	 */
	@Override
	public void disconnected() {
		// TODO Auto-generated method stub

	}

	public void RecognizedInput(String input) {
		log.debug("Recognized Input " + input);
		try {
			textServer.sendInput(input);
			sc.invoke("clientMessage", new Object[] { input });
			recgonizer.stopRecognition();
		} catch (IOException e) {
			log.error("Error sending recognized input", e);
		}
	
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.jvoicexml.client.text.TextListener#outputSsml(org.jvoicexml.xml.ssml
	 * .SsmlDocument)
	 */
	@Override
	public void outputSsml(SsmlDocument arg0) {
		log.info("SSML output");
		log.debug(arg0.getTextContent());
		// TODO Auto-generated method stub

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jvoicexml.client.text.TextListener#outputText(java.lang.String)
	 */
	@Override
	public void outputText(String arg0) {
		log.info("Text Output");
		log.debug(arg0);
		if (sc != null) {
			sc.invoke("serverMessage", new Object[] { arg0 });
		}
		recgonizer.startRecognition();
	}

	public void close() {
		log.info("Closing session " + id);
		textServer.stopServer();
		recgonizer.close();
	}

	public boolean isRecognizerReady() {
		return recognizerReady;
	}

	public void setRecognizerReady(boolean recognizerReady) {
		this.recognizerReady = recognizerReady;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public TextServer getTextServer() {
		return textServer;
	}

	public void setTextServer(TextServer textServer) {
		this.textServer = textServer;
	}

	public Session getSession() {
		return session;
	}

	public void setSession(Session session) {
		this.session = session;
	}

	public SpeechRecognizer getRecgonizer() {
		return recgonizer;
	}

	public void setRecgonizer(SpeechRecognizer recgonizer) {
		this.recgonizer = recgonizer;
	}

	public IConnection getConn() {
		return conn;
	}

	public void setConn(IConnection conn) {
		this.conn = conn;
	}

	public Application getApp() {
		return app;
	}

	public void setApp(Application app) {
		this.app = app;
	}

}
